package com.cml.batisext.core.repository.mybatis.interceptor;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import com.cml.batisext.core.bean.base.DatabaseBean;
import com.cml.batisext.core.exception.DataHaveChangedException;
import com.cml.batisext.core.exception.VersionNotExistException;
import com.cml.batisext.core.repository.mybatis.annotations.GenerateDao;
import com.cml.batisext.core.repository.mybatis.base.BaseDao;
import com.cml.batisext.core.repository.mybatis.base.BaseDaoSqlProvider;

/**
 * mybatis 拦截器，用来实现通用接口，以及更新的version校验
 * @author Administrator
 *
 */
@Component
@Intercepts({
		@Signature(type = Executor.class, method = "query", args = {
				MappedStatement.class, Object.class, RowBounds.class,
				ResultHandler.class }),
		@Signature(type = Executor.class, method = "update", args = {
				MappedStatement.class, Object.class }) })
public class BaseDaoInterceptor implements Interceptor {

	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	private static final ReflectorFactory DEFAULT_OBJECT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
	public Object intercept(Invocation invocation) throws Throwable {

		Object[] queryArgs = invocation.getArgs();

		final MappedStatement ms = (MappedStatement) queryArgs[0];
        boolean isVerifyExecute = false;
        boolean needValidVersion = false;
        //批量执行条数
        int batchExecuteCount = 0;
		if(isBaseDaoMethod(ms.getId())){
			// 获取查询的dao class对象
			Class<?> dao = getTargetDaoClass(ms.getId());
			// 获取dao被调用的方法对象
			// Method method = getTargetDaoMethodName(ms.getId(),dao,parameter);
			Class<?> beanType = getBeanType(dao);
			// 告诉SqlProvider按照指定类型实体生成sql
			BaseDaoSqlProvider.currentResultBeanClass.set(beanType);
			// 指定mybits将结果集转换的类型
			setResultType(ms, beanType);
			
			Object param = queryArgs[1];

			needValidVersion = needValidVersion(ms.getSqlCommandType());
            if (needValidVersion) {
                if (!isVersionExist(param)) {
                    throw new VersionNotExistException();
                }
            }
            //判断是否是批量执行sql
            isVerifyExecute = isVerifyExecute(param);
            //如果是则获取要批量执行的条数
            if (isVerifyExecute) {
                batchExecuteCount = batchExecuteCount(param);
            }

		}

		Object result = invocation.proceed();
		
		if(needValidVersion){
			if (result instanceof Integer && ((Integer) result) == 0) {
				throw new DataHaveChangedException();
			}
	        //判断是否是批量执行sql
	        if (isVerifyExecute) {
	            //如果是则判断需要执行的条数和执行成功的条数是否一致
	            if (result instanceof Integer && ((Integer) result) !=batchExecuteCount ) {
	                throw new DataHaveChangedException();
	            }
	        }
		}
		return result;
	}
	 /**
     * 获取需要批量执行sql的条数
     * 
     * @param param
     * @return
     * @throws Exception
     */
    private Integer batchExecuteCount(Object param) throws Exception {

        if (param instanceof Map) {
            Map p = (Map) param;
            List<Object> list = (List<Object>) p.get(BaseDao.LIST_PARAM_NAME);
            if (list != null) {
                return list.size();
            }
        }
        return 0;
    }
    /**
     * 判断是否是批量执行sql
     * 
     * @param param
     * @return
     * @throws Exception
     */
    private boolean isVerifyExecute(Object param) throws Exception {

        if (param instanceof Map) {
            Map p = (Map) param;
            List<Object> list = (List<Object>) p.get(BaseDao.LIST_PARAM_NAME);
            if (list != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证version值是否存在
     * @param param
     * @return
     * @throws Exception
     */
    private boolean isVersionExist(Object param) throws Exception {
		
        if (param instanceof DatabaseBean) {
            Method method = DatabaseBean.class.getMethod("getVersion");
            Object versionValue = method.invoke(param);
            if (versionValue != null) {
                return true;
            }
        }
        if (param instanceof Map) {
            @SuppressWarnings("rawtypes")
            Map p = (Map) param;
            List<Object> list = (List<Object>) p.get(BaseDao.LIST_PARAM_NAME);
            if (list == null) {
                for (Object key : p.keySet()) {
                    param = p.get(key);

                    if (param instanceof DatabaseBean) {
                        Method method = DatabaseBean.class.getMethod("getVersion");
                        Object versionValue = method.invoke(param);
                        if (versionValue != null) {
                            return true;
                        }
                    }
                }
            } else {
            	if(list.size() == 0){
            		return true;
            	}
                for (Object listobj : list) {
                    Object versionValue = DatabaseBean.class.getMethod("getVersion").invoke(listobj);
                    if (versionValue != null) {
                        return true;
                    }
                }
            }
        }
        return false;
	}

    /**
     * 判断sql语句是否为修改或者是删除
     * @param sqlCommandType
     * @return
     * @throws Exception
     */
    private boolean needValidVersion(SqlCommandType sqlCommandType) throws Exception {

        if (sqlCommandType.equals(SqlCommandType.UPDATE)) {
            return true;
        }
        return false;
    }

    /**
    	 * 判断是否是通用接口方法
    	 * @param method
    	 * @return
    	 */
	private boolean isBaseDaoMethod(String method) {
		
		return BaseDao.baseDaoMethods.contains(method.substring(method.lastIndexOf(".") + 1));
	}

	/**
	 * 根据公共接口CLass对象获取公共接口的实体类型
	 * 
	 * @param dao
	 * @return
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	private Class<?> getBeanType(Class<?> dao) throws Exception {
		return dao.getAnnotation(GenerateDao.class).beanType();
	}

	private Class<?> getTargetDaoClass(String id) {
		try {
			return Class.forName(id.substring(0, id.lastIndexOf(".")));
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return null;
		}
	}

	private void setResultType(MappedStatement ms, Class<?> clazz) {

		if (ms.getId().endsWith("deleteById") || ms.getId().endsWith("delete")
				|| ms.getId().endsWith("update")
				|| ms.getId().endsWith("updateColumns")) {
			clazz = Integer.class;
		}
		
		if (ms.getId().endsWith("selectCount")) {
			clazz = Long.class;
		}
		
		if (ms.getId().endsWith("insert")) {
			clazz = void.class;
		}

		ResultMap resultMap = ms.getResultMaps().get(0);
		MetaObject metaObject = forObject(resultMap);
		metaObject.setValue("type", clazz);
	}

	public static MetaObject forObject(Object object) {
		return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
				DEFAULT_OBJECT_WRAPPER_FACTORY,DEFAULT_OBJECT_REFLECTOR_FACTORY);
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {

	}

}
